/*
 项目部署(产品发布)的流程：
   1. 购买服务器
     + 推荐“阿里云”服务器「云服务器ECS」
       + 外网IP  例如：123.57.205.204
     + 基于FTP上传工具(FileZilla)把开发的代码上传到服务器
     + 基于Nginx、Apache、Node、IIS等工具部署产品
       + Linux操作系统：Nginx或Apache {最常用}
       + Window操作系统：IIS
       + 一台服务器可以部署多个项目，我们基于端口号来区分「端口号：0~65535之间」

   2. 够买域名
     第一步结束后，我们可以基于“外网IP+端口号”访问到部署的产品了，只不过地址太难记忆了，所以我们期望找一个好记忆的名字，来代替外网IP，而这就是域名！！
     + DNS解析：让域名和服务器外网IP关联起来
       原理是把解析记录放在了DNS服务器上（DNS服务器：域名解析服务器）
     + 备案

   域名的知识扫盲：
     顶级域名  qq.com
     一级域名  www.qq.com
     二级域名  v.qq.com
     三级域名  kbs.sports.qq.com
     ...
     ---
     我们购买域名，只需要购买“顶级域名”即可，其余级别的域名，都是我们在做域名解析时自己分配的！！
     ---
     .com国际域名  .cn中文域名  .com.cn
     .org官方  .edu学校  .gov政府  .net管理系统  .vip会员


 如果研发的产品只有静态页面（或前端程序），不需要部署后端服务，也可以基于第三方平台，免费发布作品！！
   支持的平台：github、gitee
 */

/*
 从输入URL地址到看到页面，中间都经历了啥?
    第一步：URL解析
      目的：让客户端浏览器，清楚的知道URL的每一部分信息，然后才能有针对性的发送请求
      一个完整URL所包含的部分：
      @1 传输协议
         作用是实现客户端和服务器端的数据传输！
         HTTP：超文本传输协议(除了传输文本外，还可以传输图片/音视频等富媒体资源)
         HTTPS：HTTP+SSL，经过加密处理的HTTP，所以更安全(涉及支付类的网站都是用HTTPS)
         FTP：适用于文件的传输，例如 把开发的代码文件上传到服务器进行部署，或者FTP共享资源服务器等
      @2 域名(或者IP地址)
         作用是给不好记忆的IP地址设置好记忆的名字！
         http://127.0.0.1:xxxx  => http://localhost:xxxx
      @3 端口号
         作用是区分相同服务器上的不同服务(项目)的！
         取值范围：0~65535
         默认端口号：用户在地址中不需要写，浏览器会自己加的端口号
           http -> 80
           https -> 443
           ftp -> 21
      @4 请求资源的路径名称
      @5 问号参数
         作用是:客户端向服务器发送请求，可以基于“问号传参”把信息发送给服务器！
      @6 HASH(哈希)值
         作用有:锚点定位、HASH路由...
      ----
      URL信息编码问题：如果在URL地址中出现了中文或者部分特殊符号，为了防止传输中出现乱码，需要提前对信息进行编码，后期获取信息后再解码即可！！
        + encodeURI/decodeURI：会对“中文和空格”进行编码，一般适用于编码整个URL地址！
        + encodeURIComponent/decodeURIComponent：除了对“中文和空格”进行编码，对于一些特殊符号（例如：&?#/@...）等也会进行编码，所以不能对整个URL进行处理，一般用于处理URL地址的局部内容！
        + escape/unescape：只适用于客户端两个页面之间，信息传输的编码，不适用于客户端和服务器(原因：大部分后台都不支持这个API方法，这样就无法解码)

    第二步：缓存检查

    第三步：DNS解析
      DNS解析也称为“域名解析”：就是到DNS服务器上，基于域名获取服务器的外网IP！
      情况1：第一次访问域名(本地没有任何解析缓存)，此时我们需要去DNS服务器进行解析
        + 遵循：迭代查找
        + 去三个DNS服务器依次查找，才可获取需要的结果：根域名服务器、顶级域名服务器、权威域名服务器
      情况2：如果并不是第一次访问，我们需要先在本地缓存中查找相关的解析
        + 遵循：递归查找
        + 依次去下述几个地方查找，只要找到解析记录，则停止查找：浏览器缓存、本地hosts文件、本地DNS解析器、本地DNS服务器；如果本地缓存都没找到，则按照“情况1”处理！！
      每一次DNS解析需要消耗20~120毫秒！！
      谷歌/火狐等浏览器，对于DNS解析记录会默认缓存1min左右！！

    第四步：TCP三次握手
      目的是建立客户端和服务器端“稳定”的传输通道！！
      TCP：建立稳定可靠的通信机制，但是“慢”
      UDP：不需要像TCP一样进行三次握手，直接传输，这样速度“快”，但是不一定“稳定可靠”（容易丢包）

    第五步：HTTP数据传输
      阶段一：请求阶段 Request
        客户端向服务器发送请求，把数据传递给服务器
      阶段二：响应阶段 Response
        服务器把信息返回给客户端
      HTTP事物：一套完整的Request+Response
      HTTP报文：在请求和响应过程中，传输的所有内容，统称为HTTP报文信息
        + 起始行：请求起始行、响应起始行
        + 首部(头)：请求头、响应头
        + 主体：请求主体、响应主体
      ----
      所有的前后端通信信息，在“控制台->Network”中都可以查看到！！

    第六步：TCP四次挥手
      目的是断开客户端和服务器端之间的连接通道！！
      @1 客户端把信息传给服务器后，会主动发起断开通道的请求：“我把东西给你了，你准备接收，我这边打算断开了”
      @2 服务器收到客户端发送的信息，需要“立即给予”客户端相关的反馈：东西已收到，你待会吧，我去给你准备
      服务器准备客户端需要的内容，是消耗时间的，如果之前不给予立即反馈，客户端无法准确知道，信息是否发送成功！所以多了一次挥手的过程，这也是为啥挥手是四次，而握手只需要三次！！
      @3 服务器把准备好的信息返回给客户端，同时主动断开连接：“东西给你了，你准备接收吧，以后别找我了...”
      @4 客户端收到服务器返回的内容，给服务器反馈信息：“东西我收到了，互相拉黑吧...”
      ----
      如果每一次请求都是“三握四挥”，则非常消耗性能和浪费时间，我们期望：第一次建立通道后，先不要释放通道，后续的请求依然在这个通道中进行通信即可！！ =>  设置 Connection: keep-alive 「请求头和响应头」：保持TCP通道的长链接！！
        + 服务端设置：可以按照过期时间设置，也可以按照请求数量设置！！
        + HTTP/1.1版本默认就加了Connection:keep-alive

    第七步：客户端渲染
      + DOM TREE
      + CSSOM TREE
      + RENDER TREE
      + Layout
      + 分层
      + Painting
 */

/* let url = `http://www.xxx.com/index.html?from=${encodeURIComponent('http://www.qq.com/')}&lx=${encodeURIComponent('1')}&name=${encodeURIComponent('珠峰培训')}`;
console.log(url); //'http://www.xxx.com/index.html?from=http%3A%2F%2Fwww.qq.com%2F&lx=1&name=%E7%8F%A0%E5%B3%B0%E5%9F%B9%E8%AE%AD' */

/*
浏览器中的缓存机制
  针对静态资源文件：强缓存、协商缓存
    静态资源文件一般指html/css/js/image等资源
    无论何种缓存，最后总要在本地存储一份，存储的位置？
      + 内存缓存 Memory Cache ：存储在虚拟内存中
      + 硬盘缓存 Disk Cache ：存储在物理内存中
    如果是页面刷新(F5)，则先在“内存缓存”中查找，找不到再去“硬盘缓存”找
    如果是页面关闭再打开，则直接去“硬盘缓存”中找
    如果是强制刷新页面(CTRL+F5)，则不去缓存中找，直接去服务器获取最新的
    去不同地方获取资源信息，所需要的时间排序：内存缓存中找(时间几乎为0ms) < 硬盘缓存中找 < 从服务器获取

  强缓存机制
    只要本地有缓存且未过期，则从本地缓存中获取资源，反之才去服务器获取！
    如何设置强缓存？
      + 由服务器设置
      + 当我们第一次访问页面(本地没有任何缓存)，需要去服务器端获取资源；服务器在返回相关资源的同时，会在“响应头”中设置两个字段(或者设置其中一个)：
        + Expires ：存储过期时间(具体日期) Sun, 04 Sep 2022 08:07:02 GMT  「HTTP/1.0」
        + Cache-Control ：存储的是一个“秒数”，代表多久后过期  2592000  「HTTP/1.1」
      + 客户端浏览器在接收服务器返回的资源后，除了页面渲染，如果发现有这两个字段，则“默认”会把获取的资源和字段标识缓存在本地「不需要前端写代码」！！
      + 当客户端下一次访问这个页面，先检查本地的缓存和标识，如未过期，则直接从缓存读取！！
    在强缓存机制下，不论是从服务器获取，还是从本地缓存中获取，最后返回的状态码都是200！！
    强缓存机制存在的问题：如何保证，服务器资源更新后，即便客户端本地有缓存，也要从服务器获取最新的资源信息，只有这样才能保证，随时看到最新的内容？
      + 因为一个页面的渲染是从html开始的（渲染html的过程中,遇到link/script/img等标签,才会去获取其它资源），所以html页面“坚决不能设置强缓存机制”！！
      + 然后只要保证，其它资源一但被更新，会产生新的文件名（可以基于webpack实现），而html中导入的也是新的文件，这样就可以解决实时更新的问题了！！

  协商缓存机制
    即便本地有缓存(且未过期)，也需要向服务器发送请求，问一问服务器，从上次获取到现在，当前资源有没有更新过；
      + 没更新过：返回304状态码(不返回资源信息)；浏览器发现是304，则直接去缓存中获取资源即可！！
      + 有更新过：返回200状态码及最新的资源信息；浏览器把最新的信息和标识，重新缓存到本地即可！！
    如何设置协商缓存？
      + 也是由服务器设置的
      + 服务器端在返回信息的同时，在响应头中设置两个字段：
        + Last-Modified : 存储资源最后一次更新的时间(最小单位是秒) Mon, 29 Nov 2021 08:08:24 GMT 「HTTP/1.0」
        + ETag : 存储资源最后一次更新产生的唯一标识 "61a48a78-11d0"  「HTTP/1.1」
      + 客户端接收信息，并且把资源和标识都存储起来！
      + 当客户端下一次发送请求（向服务器发送请求）的时候，需要在请求头中，基于 If-Modified-Since 或 If-None-Match 分别把 Last-Modified 和 ETag 的值传递给服务器！
      + 服务器拿传递的时间和标识，与服务器资源最后一次更新的时间和标识做比较；如果相同说明没更新过，如果不同则说明有更新！！

总结：
  1. 不论是“强缓存”还是“协商缓存”，都是针对于第二次及以后访问页面所出现的机制(第一次访问可以建立缓存)，而且都是针对于“静态资源文件”的！
  2. “强缓存”和“协商缓存”都是由服务器设置「响应头中返回相关字段」，客户端浏览器会自动配合完成缓存信息和缓存检查等操作，不需要前端开发者写代码！！
  3. 从消耗性能和时间上对比：强缓存性能最好，其次是协商缓存，最后是不设置缓存(每一次都从服务器获取)
  4. 如果强缓存和协商缓存机制都设置了，则需要在强缓存失效的情况下才进行协商缓存「所以有人说：协商缓存是强缓存的一种补充」，真实项目中：
    + html页面，坚决不能设置强缓存，但是可以设置协商缓存（也可以不做缓存）
    + 其余静态资源，建议强缓存和协商缓存都设置即可！！
 */

/*
数据缓存：真实项目中，我们往往需要基于ajax向服务器发送请求，从服务器获取数据；但是某些数据并不会经常更新，为了防止频繁刷新页面，总是要向服务器发送请求拿数据（而且拿到的数据是一样的），我们期望可以像“强缓存机制”一样，构建一套数据缓存机制！！
  + 针对“不经常更新数据”的请求
  + 缓存的时间不要过长「一般用于防止频繁刷新页面」

实现思路：
  每一次加载页面，首先看本地是否存储过相关的数据(而且是否过期)，如果有且未过期，则直接获取本地缓存的信息即可，反之从服务器重新获取（获取最新信息后，要存储到本地）！！
  备注：我们基于localStorage进行本地存储，但是需要手动设计一套"存储有效期"的方案!
    + 存储信息的时候，把当前时间也存储进去
    + 后期获取信息的时候
      + 首先拿当前最新时间，减去之前存储的时间，算出时间差
      + 再判断是否在有效期内
      + 如果在有效期，则把存储的信息获取到
      + 如果不在有效期，则删除并且返回null

客户端“本地存储”方案：
  不论何种方案，都是基于“明文”方式进行存储：所以不要存储“需要保证安全”的信息(例如:密码)，如果非要存储，一定要记得加密！！
  而且存储的信息会受“源和浏览器”的限制：不能跨浏览器访问、不能跨源访问...
  @1 cookie
    基于document.cookie来进行管理
    + 大小限制：同源下最多只允许存储4KB内容
    + 过期时间：设置cookie信息的时候，需要指定过期时间
    + 稳定性：基于cookie存储的信息是不稳定的，清除历史记录、或者使用清扫垃圾的软件，都有可能把存储的cookie干掉(哪怕还未过期)！
    + 禁止性：浏览器开启“无痕浏览”、“隐私”等模式，都是禁用cookie的
    + 此客户端存储和服务器还是有“猫腻儿”的
      + 只要客户端存储了cookie信息，每一次向服务器发送请求，都会在“请求头Cookie字段”中，把cookie信息传递给服务器（不管服务器是不是需要这些信息）「排除跨域请求」
      + 服务器返回信息的时候，只要在“响应头设置set-cookie字段”，则客户端自己就会把set-cookie的信息，在本地设置cookie存储起来！！
    + 兼容性：兼容IE5
  @2 localStorage
    基于localStorage.setItem/getItem/removeItem/clear等方法来操作
    + 大小限制：同源下最多只允许存储5MB
    + 过期时间：持久化存储，只要不手动删除，则一直会存储着
    + 稳定性&禁止性：清除历史记录或者开启隐私模式等，对localStorage是无效的！
    + 和服务器没啥关系，当然可以手动写代码，把本地基于localStorage存储的信息发送给服务器！
    + 兼容性：不兼容IE6~8
  @3 sessionStorage
    语法上和localStorage几乎完全一致！唯一的区别是：
    localStorage(或者cookie)存储的信息，在页面刷新和关闭的时候，不会释放掉！但是sessionStorage是会话存储，也就是：页面刷新，存储的信息还在，但是页面一但关闭，存储的信息都会被释放掉！
  @4 全局变量/vuex/redux
    存储在虚拟内存中，理论上来讲是没有大小限制的
    页面刷新或者关闭，存储的信息都会释放掉！！
  @5 浏览器数据库存储：IndexedDB 或者 Web SQL
  ...
 */


/* 实现具备有效期的LocalStorage */
const storage = {
  // 存储信息:存储当前时间
  set(key, value) {
    value = {
      time: +new Date(),
      data: value
    };
    localStorage.setItem(key, JSON.stringify(value));
  },
  // 获取信息:有效期校验
  get(key, cycle = 60 * 60 * 1000) {
    let value = localStorage.getItem(key);
    if (!value) return null;
    let { time, data } = JSON.parse(value);
    if ((+new Date() - time) > cycle) {
      // 过期了
      storage.remove(key);
      return null;
    }
    // 信息有效
    return data;
  },
  // 移除信息
  remove(key) {
    localStorage.removeItem(key);
  }
};

/* 实现数据缓存 */
const query = () => {
  return new Promise(resolve => {
    let xhr = new XMLHttpRequest;
    xhr.open('GET', './package.json');
    xhr.onreadystatechange = () => {
      let { readyState, status, responseText } = xhr;
      if (readyState === 4 && status === 200) {
        // 异步Ajax请求成功
        resolve(JSON.parse(responseText));
      }
    };
    xhr.send();
  });
};

(async () => {
  // 先检测本地是否有缓存
  let cache = storage.get('cache', 10000);
  if (cache) {
    console.log('成功「缓存」', cache);
    return;
  }
  // 向服务器发送请求
  let data = await query();
  console.log('成功「服务器」', data);
  // 获取数据后缓存到本地
  storage.set('cache', data);
})();

/*
 HTTP状态码
   客户端向服务器发送请求，服务器不一定都能返回客户端想要的，可能会存在多种情况，此时服务器会基于“HTTP状态码”告诉客户端，返回的结果是啥情况！

   以2开始的：一般都是成功
    + 200 OK 成功「把数据成功返回给客户端」
    + 206 Partial Content 断点续传(部分成功)

   以3开始的：也可以获取数据，只不过经历了特殊处理
    + 301 Moved Permanently 永久转移(重定向) 「适用于域名迁移或变更」
    + 302 Move Temporarily（307 Temporary Redirect） 临时转移(重定向) 「适用于服务器负载均衡」
    + 304 Not Modified 协商缓存中，服务器资源没更新，返回的就是304
   
   以4开始的：错误(一般都是客户端错误)
    + 400 Bad Request 请求参数有误
    + 401 Unauthorized 无权限访问
    + 403 Forbidden 错了，但是老子不告诉你为啥
    + 404 Not Found 请求地址错误
    + 405 Method Not Allowed 请求方式不被允许
    + 408 Request Timeout 请求超时

   以5开始的：错误(一般是服务器错误)
    + 500 Internal Server Error  未知服务器错误「例如：服务器断点、宕机...」
    + 502 Bad Gateway  网关出现问题
    + 503 Service Unavailable  服务器超负荷
 */